﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;
using System.IO;
using System.Threading;

namespace Client.p2p
{
    public class GlobalCmdBackground : CommonCmdBackground
    {
        public IPEndPoint MulticastEP = new IPEndPoint(IPAddress.Parse(Properties.Resources.MulticastIP2Broadcast), int.Parse(Properties.Resources.MulticastPort));
        /// <summary>
        /// TCP端口监听类
        /// </summary>
        private TcpListener tcpListener;
        /// <summary>
        /// TCP端口监听后台
        /// </summary>
        private BackgroundWorker bwTcpWorker;
        /// <summary>
        /// UDP客户类
        /// </summary>
        private UdpClient udpClient = null;
        /// <summary>
        /// UDP端口监听后台
        /// </summary>
        private BackgroundWorker bwUdpWorker;
        /// <summary>
        /// 重用多播端口监听后台
        /// </summary>
        private BackgroundWorker bwMulticastWorker;
        /// <summary>
        /// 当前使用端口
        /// </summary>
        private int listenPort;
        public int Port
        {
            get { return listenPort; }
        }
        /// <summary>
        /// TCP监听端口最大队列
        /// </summary>
        private int listenBackLog;
        public IPEndPoint LocalEP;
        private UserSession session;
        public UserSession Session
        {
            get { return session; }
            set { session = value; }
        }

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        public GlobalCmdBackground()
        {
            this.listenPort = int.Parse(global::Client.Properties.Resources.ListenPort);
            this.listenBackLog = int.Parse(global::Client.Properties.Resources.ListenBacklog);
            this.bwTcpWorker = new BackgroundWorker();
            this.bwTcpWorker.WorkerSupportsCancellation = true;
            this.bwTcpWorker.DoWork += new DoWorkEventHandler(bwTcpWorker_DoWork);
            this.bwTcpWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwTcpWorker_RunWorkerCompleted);
            this.bwUdpWorker = new BackgroundWorker();
            this.bwUdpWorker.WorkerSupportsCancellation = true;
            this.bwUdpWorker.DoWork += new DoWorkEventHandler(bwUdpWorker_DoWork);
            this.bwUdpWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwUdpWorker_RunWorkerCompleted);
            this.bwMulticastWorker = new BackgroundWorker();
            this.bwMulticastWorker.WorkerSupportsCancellation = true;
            this.bwMulticastWorker.DoWork += new DoWorkEventHandler(bwMulticastWorker_DoWork);
            this.bwMulticastWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwMulticastWorker_RunWorkerCompleted);
            this.bwTcpWorker.RunWorkerAsync();
            this.bwUdpWorker.RunWorkerAsync();
            this.bwMulticastWorker.RunWorkerAsync();
        }

        ~GlobalCmdBackground()
        {
            this.bwTcpWorker.CancelAsync();
            this.bwUdpWorker.CancelAsync();
            this.bwMulticastWorker.CancelAsync();
        }

        #endregion

        #region Receive Works

        private Semaphore listenPortSemaphore = new Semaphore(0, 1);
        //问题遗留：只检测TCP端口是否被占用，并没有检测UDP对应端口

        private void bwTcpWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            while (true)
            {
                try
                {
                    this.tcpListener = new TcpListener(IPAddress.Any, this.listenPort);
                    this.tcpListener.Start(this.listenBackLog);
                    this.listenPortSemaphore.Release();
                    System.Diagnostics.Debug.Print("TcpWorker Begin");
                    while (!bw.CancellationPending)
                    {
                        TcpClient tcpClient = this.tcpListener.AcceptTcpClient();
                        BackgroundWorker bwProcess = new BackgroundWorker();
                        bwProcess.WorkerSupportsCancellation = true;
                        bwProcess.DoWork += new DoWorkEventHandler(TcpCommandProcess);
                        bwProcess.RunWorkerCompleted += new RunWorkerCompletedEventHandler(TcpCmdProcessCompleted);
                        bwProcess.RunWorkerAsync(tcpClient);
                    }
                    e.Result = true;
                    break;
                }
                catch (SocketException ex)
                {
                    if (((SocketException)ex).SocketErrorCode == SocketError.AddressAlreadyInUse)
                    {
                        this.listenPort++;
                        continue;
                    }
                    else
                    {
                        MessageBox.Show(ex.Message);
                        e.Result = false;
                        break;
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Print(ex.Message);
                    e.Result = false;
                    break;
                }
            }
            tcpListener.Stop();
        }

        private void bwTcpWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        /// <summary>
        /// Command processing for TCP port
        /// </summary>
        private void TcpCommandProcess(object sender, DoWorkEventArgs e)
        {
            TcpClient tcpClient = (TcpClient)e.Argument;
            BackgroundWorker bw = sender as BackgroundWorker;
            try
            {
	            NetworkStream networkStream = tcpClient.GetStream();
	            while (!bw.CancellationPending)
	            {
		            //Command Processing
	                byte[] buffer = new byte[4];
	                int readBytes = networkStream.Read(buffer, 0, 4);
	                if (readBytes == 0) break;
	                CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
	                buffer = new byte[4];
	                readBytes = networkStream.Read(buffer, 0, 4);
                    if (readBytes == 0) break;
	                int senderNameSize = BitConverter.ToInt32(buffer, 0);
	                buffer = new byte[senderNameSize];
	                readBytes = networkStream.Read(buffer, 0, senderNameSize);
                    if (readBytes == 0) break;
	                string senderName = Encoding.Unicode.GetString(buffer);
	                buffer = new byte[4];
	                readBytes = networkStream.Read(buffer, 0, 4);
                    if (readBytes == 0) break;
	                int metaDataSize = BitConverter.ToInt32(buffer, 0);
                    if (metaDataSize > 0)
                    {
                        buffer = new byte[metaDataSize];
                        readBytes = networkStream.Read(buffer, 0, metaDataSize);
                    }
                    else buffer = null;
                    if (readBytes == 0) break;
//                     if (cmdType == CommandType.TcpClose) break;
	                OnCommandReceived(new CommandEventArgs(new Command(cmdType,
                        (IPEndPoint)tcpClient.Client.RemoteEndPoint,
                        senderName,
                        buffer)));
//                     if (cmdType == CommandType.Message)
//                     {
//                         e.Result = true;
//                         semaphores.Remove(tcpClient);
//                         return;
//                     }
	            }
                System.Diagnostics.Debug.Print("TcpCmdProc Ended!");
                e.Result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
                e.Result = false;
            }
//             semaphores.Remove(tcpClient);
            tcpClient.Close();
       }

        private void TcpCmdProcessCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        private void bwUdpWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            this.listenPortSemaphore.WaitOne();
            System.Diagnostics.Debug.Print("UdpWorker Begin");
            try
            {
                this.udpClient = new UdpClient(listenPort);
                //注释下面代码，作专门用途
//                 this.udpClient.EnableBroadcast = true;
//                 this.udpClient.JoinMulticastGroup(IPAddress.Parse(global::Client.Properties.Resources.MulticastIP2Broadcast));
                this.LocalEP = (IPEndPoint)this.udpClient.Client.LocalEndPoint;
                while (!bw.CancellationPending)
                {
                    IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                    Byte[] receiveBytes = udpClient.Receive(ref remoteEP);
                    UdpCommandProcess(receiveBytes, remoteEP);
                }
                e.Result = true;
                System.Diagnostics.Debug.Print("UdpCmdProc Ended!");
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
                e.Result = false;
            }
            if (this.udpClient != null) this.udpClient.Close();
        }

        private void bwUdpWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        /// <summary>
        /// Command processing for UDP port
        /// </summary>
        private void UdpCommandProcess(Byte[] receiveBytes, IPEndPoint remoteEP)
        {
/*
            Stream stream = new MemoryStream(receiveBytes);
            byte[] buffer = new byte[4];
            stream.Read(buffer, 0, 4);
            CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
            stream.Read(buffer, 0, 4);
            int senderNameSize = BitConverter.ToInt32(buffer, 0);
            buffer = new byte[senderNameSize];
            string senderName = Encoding.Unicode.GetString(buffer, 0, senderNameSize);
            //string metaData = Encoding.ASCII.GetString(receiveBytes, 8 + senderNameSize, receiveBytes.Length - 8 - senderNameSize);
            int metaDataSize = receiveBytes.Length - 8 - senderNameSize;
            buffer = new byte[metaDataSize];
            stream.Read(buffer, 0, metaDataSize);
            OnCommandReceived(new CommandEventArgs(new Command(cmdType, this.LocalEP, remoteEP.Address, senderName, buffer)));
*/

            try
            {
	            CommandType cmdType = (CommandType)BitConverter.ToInt32(receiveBytes, 0);
	            int senderNameSize = BitConverter.ToInt32(receiveBytes, 4);
	            byte[] buffer = new byte[senderNameSize];
	            string senderName = Encoding.Unicode.GetString(receiveBytes, 8, senderNameSize);
                int metaDataSize = BitConverter.ToInt32(receiveBytes, 8 + senderNameSize);
	            buffer = new byte[metaDataSize];
	            Array.Copy(receiveBytes, 12 + senderNameSize, buffer, 0, metaDataSize);
	            OnCommandReceived(new CommandEventArgs(new Command(cmdType, remoteEP, senderName, buffer)));
            }
            catch (System.Exception e)
            {
//                 MessageBox.Show(e.Message);
                System.Diagnostics.Debug.Print("从{0}来的未知{1}字节数据！\n", remoteEP.ToString(), receiveBytes.Length.ToString());
            }
        }

        private void bwMulticastWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            System.Diagnostics.Debug.Print("MulticastWorker Begin");
            UdpClient udp = new UdpClient();
            try
            {
                udp.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                udp.Client.Bind(new IPEndPoint(IPAddress.Any, int.Parse(global::Client.Properties.Resources.MulticastPort)));
                udp.JoinMulticastGroup(IPAddress.Parse(Properties.Resources.MulticastIP2Broadcast));
                udp.MulticastLoopback = true;
//                 udp.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership,
//                     new MulticastOption(IPAddress.Parse(Properties.Resources.MulticastIP2Broadcast)));
                while (!bw.CancellationPending)
                {
                    IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                    Byte[] receiveBytes = udp.Receive(ref remoteEP);
                    UdpCommandProcess(receiveBytes, remoteEP);
                }
                e.Result = true;
                System.Diagnostics.Debug.Print("MulticastWorker Ended!");
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
                e.Result = false;
            }
            if (udp != null) udp.Close();
        }

        private void bwMulticastWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        #endregion

        #region Send Works

        private void SendTcpCommand(Command cmd)
        {
            BackgroundWorker bwSendTcp = new BackgroundWorker();
            bwSendTcp.WorkerSupportsCancellation = true;
            bwSendTcp.DoWork += new DoWorkEventHandler(bwSendTcp_DoWork);
            bwSendTcp.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwSendTcp_RunWorkerCompleted);
            bwSendTcp.RunWorkerAsync(cmd);
        }

        private void bwSendTcp_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

//         private Dictionary<TcpClient, System.Threading.Semaphore> semaphores = new Dictionary<TcpClient, System.Threading.Semaphore>();
// 
        private void bwSendTcp_DoWork(object sender, DoWorkEventArgs e)
        {
            Command cmd = (Command)e.Argument;
            try
            {
//                 System.Threading.Semaphore semaphore;
                TcpClient tcpClient = new TcpClient();
                tcpClient.Connect(cmd.TargetEP);
//                 semaphore = new System.Threading.Semaphore(1, 1);
//                 this.semaphores.Add(tcpClient, semaphore);
//                 semaphore.WaitOne();
                NetworkStream networkStream = tcpClient.GetStream();
                //Command Type
                byte[] buffer = BitConverter.GetBytes((int)cmd.CommandType);
                networkStream.Write(buffer, 0, 4);
                //SenderName
                buffer = BitConverter.GetBytes(Encoding.Unicode.GetByteCount(session.Name));
                networkStream.Write(buffer, 0, 4);
                buffer = Encoding.Unicode.GetBytes(session.Name);
                networkStream.Write(buffer, 0, buffer.Length);
                //MetaData
                if (cmd.MetaData == null)
                {
                    networkStream.Write(new byte[4] { 0, 0, 0, 0 }, 0, 4);
                }
                else
                {
                    buffer = BitConverter.GetBytes(cmd.MetaData.Length);
                    networkStream.Write(buffer, 0, 4);
                    networkStream.Write(cmd.MetaData, 0, cmd.MetaData.Length);
                }
                networkStream.Flush();
//                 semaphore.Release();
//                 if (cmd.TcpClient == null)
//                 {
// 	                BackgroundWorker bwReceiver = new BackgroundWorker();
// 	                bwReceiver.WorkerSupportsCancellation = true;
// 	                bwReceiver.DoWork += new DoWorkEventHandler(TcpCommandProcess);
// 	                bwReceiver.RunWorkerCompleted += new RunWorkerCompletedEventHandler(TcpCmdProcessCompleted);
// 	                bwReceiver.RunWorkerAsync(tcpClient);
//                 }
                tcpClient.Close();
                OnCommandSent(new CommandEventArgs(cmd));
                e.Result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
                OnCommandFailed(new CommandEventArgs(cmd));
                e.Result = false;
            }
        }

        private void SendUdpCommand(Command cmd)
        {
            BackgroundWorker bwSendUdp = new BackgroundWorker();
            bwSendUdp.WorkerSupportsCancellation = true;
            bwSendUdp.DoWork += new DoWorkEventHandler(bwSendUdp_DoWork);
            bwSendUdp.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwSendUdp_RunWorkerCompleted);
            bwSendUdp.RunWorkerAsync(cmd);
        }

        private void bwSendUdp_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((bool)e.Result)
            {

            }
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        private void bwSendUdp_DoWork(object sender, DoWorkEventArgs e)
        {
            Command cmd = (Command)e.Argument;
            try
            {
//                 UdpClient udpClient = new UdpClient();
//                 udpClient.EnableBroadcast = true;
//                 udpClient.Connect(cmd.TargetEP);
                Stream stream = new MemoryStream();
                //Command Type
                byte[] buffer = BitConverter.GetBytes((int)cmd.CommandType);
                stream.Write(buffer, 0, 4);
                //SenderName
                if (cmd.SenderName == null) cmd.SenderName = session.Name;
                buffer = BitConverter.GetBytes(Encoding.Unicode.GetByteCount(cmd.SenderName));
                stream.Write(buffer, 0, 4);
                buffer = Encoding.Unicode.GetBytes(cmd.SenderName);
                stream.Write(buffer, 0, buffer.Length);
                //MetaData
                if (cmd.MetaData == null)
                {
                    stream.Write(new byte[4] { 0, 0, 0, 0 }, 0, 4);
                }
                else
                {
	                buffer = BitConverter.GetBytes(cmd.MetaData.Length);
	                stream.Write(buffer, 0, 4);
	                stream.Write(cmd.MetaData, 0, cmd.MetaData.Length);
                }
                stream.Flush();
                buffer = new byte[stream.Length];
                stream.Seek(0, SeekOrigin.Begin);
                stream.Read(buffer, 0, buffer.Length);
                this.udpClient.Send(buffer, buffer.Length, cmd.TargetEP);
//                 udpClient.Close();
                OnCommandSent(new CommandEventArgs(cmd));
                e.Result = true;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
                OnCommandFailed(new CommandEventArgs(cmd));
                e.Result = false;
            }
        }

        #endregion

        #region Public Methods

        public void SendCommand(Command cmd)
        {
            switch(cmd.CommandType)
            {
                case CommandType.QueryLatestUserData:
                case CommandType.UserLogoffInform:
                case CommandType.UserLogonInform:
                case CommandType.UserStillOnline:
                case CommandType.AskIfOnline:
                    SendUdpCommand(cmd);
                    break;
                case CommandType.QueryLatestUserDataAck:
                case CommandType.UserOnlineAck:
                case CommandType.UserHasAlreadyOnline:
                case CommandType.FileTransferAck:
                case CommandType.FileTransferApply:
                case CommandType.FileTransferCancel:
                case CommandType.FileTransferDeny:
                case CommandType.Message:
                    SendTcpCommand(cmd);
                    break;
                default:
                    System.Diagnostics.Debug.Print("未处理的发送消息！");
                    System.Diagnostics.Debug.Print(cmd.ToString());
                    break;
            }
        }

        #endregion

        #region Events


        #endregion
    }
}
